/*
 ------------------------------------------------------------------------------
 Copyright (C) 2009-2011 Eternal Games.

 This file is part of the Eternal Tech source code.

 The Eternal Tech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Eternal Tech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Eternal Tech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// r_world.cpp - Adds all the world surfaces to the mesh buffer for rendering
//


#include "r_local.h"


/*
 ==============================================================================

    SURFACES LIST

 ==============================================================================
*/


/*
 ==================
 R_AddSurface

 TODO: R_AddSubviewSurface
 ==================
*/
static void R_AddSurface (surface_t *surface, renderEntity_t *entity, vec3_t viewOrigin, int clipFlags)
{
	texInfo_t    *texInfo = surface->texInfo;
	material_t   *material;
	int			 c;

	// Frustum cull
	if (!R_CullSurface(surface, entity, viewOrigin, clipFlags))
		return;

	// Mark as visible for this view
	surface->viewCount = rg.viewCount;

	// Select material
	if (texInfo->next)
	{
		c = entity->frame % texInfo->numFrames;
		while (c){
			texInfo = texInfo->next;
			c--;
		}
	}

	material = texInfo->material;

	// If it has a subview, add the surface
	if (material->subview != SUBVIEW_NONE)
	{
		if (!r_skipSuppress->integerValue && rg.renderViewParms.subview != SUBVIEW_NONE)
			return;

		//R_AddSubviewSurface(material, entity, surface);
	}

	rg.pc.surfaces++;

	// Add it
	R_AddMeshToList(MESH_SURFACE, surface, material, entity);
}


/*
 ==============================================================================

    INLINE MODELS

 ==============================================================================
*/


/*
 ==================
 R_AddInlineModel

 TODO: R_CullEntityBox always returns
 ==================
*/
void R_AddInlineModel (renderEntity_t *entity)
{
	model_t		*model = entity->model;
	surface_t   *surface;
	int			clipFlags;
	int		    i;

	model = NULL;
	if (!model)
		return;

	// Frustum cull
	if (entity->depthHack)
		clipFlags = CULL_IN;
	else
	{
		clipFlags = R_CullEntitySphere(entity, model->surfaces->origin, model->radius, rg.renderViewParms.planeBits);

		if (clipFlags == CULL_OUT)
			return;

		if (clipFlags != CULL_IN)
		{
			clipFlags = R_CullEntityBox(entity, model->mins, model->maxs, clipFlags);

			if (clipFlags == CULL_OUT)
				return;
		}
	}

	// Mark as visible for this view
	entity->viewCount = rg.viewCount;

	rg.pc.entities++;

	// If it has a single surface, no need to cull again
	if (model->numSurfaces == 1)
		clipFlags = CULL_IN;

	// Add all the surfaces
	surface = model->surfaces + model->firstModelSurface;
	for (i = 0; i < model->numModelSurfaces; i++, surface++)
	{
		if (surface->texInfo->flags & SURF_SKY)
			continue;   // Don't bother drawing

		if (!surface->texInfo->material->numStages)
			continue;	// Don't bother drawing

		// Add the surface
		R_AddSurface(surface, entity, rg.renderView.viewOrigin, clipFlags);
	}
}


/*
 ==============================================================================

    WORLD MODEL

 ==============================================================================
*/


/*
 ==================
 R_PointInLeaf
 ==================
*/
leaf_t *R_PointInLeaf (const vec3_t point)
{
	node_t	*node;
	int		side;

	if (!rg.worldModel || !rg.worldModel->nodes)
		Com_Error(ERR_DROP, "R_PointInLeaf: NULL worldModel");

	node = rg.worldModel->nodes;
	while (1){
		if (node->contents != -1)
			return (leaf_t *)node;

		side = PointOnPlaneSide(point, 0.0f, node->plane);

		if (side == PLANESIDE_FRONT)
			node = node->children[0];
		else
			node = node->children[1];
	}
	
	return NULL;   // Never reached
}

/*
 ==================
 R_DecompressVis
 ==================
*/
static void R_DecompressVis (const byte *in, byte *out)
{
	byte	*vis;
	int		c, row;

	row = (rg.worldModel->vis->numClusters+7)>>3;
	vis = out;

	if (!in)
	{
		// No vis info, so make all visible
		while (row){
			*vis++ = 0xFF;
			row--;
		}

		return;
	}

	do {
		if (*in)
		{
			*vis++ = *in++;
			continue;
		}

		c = in[1];
		in += 2;
		while (c){
			*vis++ = 0;
			c--;
		}
	} while (vis - out < row);
}

/*
 ==================
 R_ClusterPVS
 ==================
*/
void R_ClusterPVS (int cluster, byte *vis)
{
	if (cluster == -1 || !rg.worldModel || !rg.worldModel->vis)
	{
		memset(vis, 0xFF, MAX_MAP_LEAFS/8);
		return;
	}

	R_DecompressVis((byte *)rg.worldModel->vis + rg.worldModel->vis->bitOfs[cluster][VIS_PVS], vis);
}

/*
 ==================
 R_MarkLeaves
 ==================
*/
static void R_MarkLeaves ()
{
	byte	vis[MAX_MAP_LEAFS/8], vis2[MAX_MAP_LEAFS/8];
	node_t	*node;
	leaf_t	*leaf;
	vec3_t	viewOrigin;
	int		i, c, n;

	// Current view cluster
	if (rg.renderViewParms.subview == SUBVIEW_MIRROR)
	{
		rg.oldViewCluster = rg.viewCluster;
		rg.viewCluster = -1;

		rg.oldViewCluster2 = rg.viewCluster2;
		rg.viewCluster2 = -1;
	}
	else
	{
		leaf = R_PointInLeaf(rg.renderView.viewOrigin);

		if (r_showCluster->integerValue)
			Com_Printf("Cluster: %i, Area: %i\n", leaf->cluster, leaf->area);

		rg.oldViewCluster = rg.viewCluster;
		rg.viewCluster = leaf->cluster;

		rg.oldViewCluster2 = rg.viewCluster2;
		rg.viewCluster2 = leaf->cluster;

		// Check above and below so crossing solid water doesn't draw wrong
		if (!leaf->contents)
		{
			// Look down a bit
			VectorCopy(rg.renderView.viewOrigin, viewOrigin);
			viewOrigin[2] -= 16;

			leaf = R_PointInLeaf(viewOrigin);
			if (!(leaf->contents & CONTENTS_SOLID) && (leaf->cluster != rg.viewCluster2))
				rg.viewCluster2 = leaf->cluster;
		}
		else
		{
			// Look up a bit
			VectorCopy(rg.renderView.viewOrigin, viewOrigin);
			viewOrigin[2] += 16;

			leaf = R_PointInLeaf(viewOrigin);
			if (!(leaf->contents & CONTENTS_SOLID) && (leaf->cluster != rg.viewCluster2))
				rg.viewCluster2 = leaf->cluster;
		}
	}

	if (rg.viewCluster == rg.oldViewCluster && rg.viewCluster2 == rg.oldViewCluster2 && !r_skipVisibility->integerValue && rg.viewCluster != -1)
		return;

	// Development aid to let you run around and see exactly where the
	// PVS ends
	if (r_lockPVS->integerValue)
		return;

	rg.visCount++;
	rg.oldViewCluster = rg.viewCluster;
	rg.oldViewCluster2 = rg.viewCluster2;

	if (r_skipVisibility->integerValue || !rg.worldModel->vis || (rg.viewCluster == -1 && rg.renderViewParms.subview != SUBVIEW_MIRROR))
	{
		// Mark everything if needed
		for (i = 0, leaf = rg.worldModel->leafs; i < rg.worldModel->numLeafs; i++, leaf++)
			leaf->visCount = rg.visCount;
		for (i = 0, node = rg.worldModel->nodes; i < rg.worldModel->numNodes; i++, node++)
			node->visCount = rg.visCount;

		return;
	}

	// Grab PVS
	if (rg.renderViewParms.subview == SUBVIEW_MIRROR)
	{
		// Combine multiple clusters
		for (n = 0; n < rg.renderSubviewParms.numSurfaces; n++)
		{
			// Offset the origin slightly
			VectorMA(rg.renderSubviewParms.surfaces[n]->origin, 0.5f, rg.renderSubviewParms.planeNormal, viewOrigin);

			leaf = R_PointInLeaf(viewOrigin);

			if (n == 0)
			{
				R_ClusterPVS(leaf->cluster, vis);
				continue;
			}

			// Combine with previous clusters
			R_ClusterPVS(leaf->cluster, vis2);

			c = (rg.worldModel->numLeafs+31)/32;
			for (i = 0; i < c; i++)
				((int *)vis)[i] |= ((int *)vis2)[i];
		}
	}
	else
	{
		// May have to combine two clusters because of solid water
		// boundaries
		R_ClusterPVS(rg.viewCluster, vis);
		if (rg.viewCluster != rg.viewCluster2)
		{
			R_ClusterPVS(rg.viewCluster2, vis2);

			c = (rg.worldModel->numLeafs+31)/32;
			for (i = 0; i < c; i++)
				((int *)vis)[i] |= ((int *)vis2)[i];
		}
	}

	// Mark the leaves and nodes that are visible from the current cluster
	for (i = 0, leaf = rg.worldModel->leafs; i < rg.worldModel->numLeafs; i++, leaf++)
	{
		if (leaf->cluster == -1)
			continue;

		// Check the PVS
		if (!(vis[leaf->cluster>>3] & BIT(leaf->cluster&7)))
			continue;

		node = (node_t *)leaf;
		do {
			if (node->visCount == rg.visCount)
				break;
			node->visCount = rg.visCount;

			node = node->parent;
		} while (node);
	}
}

/*
 ==================
 R_RecursiveWorldNode
 ==================
*/
static void R_RecursiveWorldNode (node_t *node, int clipFlags)
{
	surface_t	*surface, **mark;
	leaf_t		*leaf;
	int			i;

	if (node->contents == CONTENTS_SOLID)
		return;   // Solid

	if (node->visCount != rg.visCount)
		return;   // Not in PVS

	// Frustum cull
	if (clipFlags != CULL_IN)
	{
		clipFlags = R_CullBox(node->mins, node->maxs, clipFlags);

		if (clipFlags == CULL_OUT)
			return;
	}

	// Mark as visible for this view
	node->viewCount = rg.viewCount;

	// Recurse down the children
	if (node->contents == -1)
	{
		R_RecursiveWorldNode(node->children[0], clipFlags);
		R_RecursiveWorldNode(node->children[1], clipFlags);
		return;
	}

	// If a leaf node, draw stuff
	leaf = (leaf_t *)node;

	if (!leaf->numMarkSurfaces)
		return;

	// Check for door connected areas
	if (!r_skipAreas->integerValue && rg.renderView.areaBits)
	{
		if (!(rg.renderView.areaBits[leaf->area>>3] & BIT(leaf->area&7)))
			return;
	}

	// Add to visible mins/maxs
	AddPointToBounds(leaf->mins, rg.renderViewParms.visMins, rg.renderViewParms.visMaxs);
	AddPointToBounds(leaf->maxs, rg.renderViewParms.visMins, rg.renderViewParms.visMaxs);

	// If it has a single surface, no need to cull again
	if (leaf->numMarkSurfaces == 1)
		clipFlags = CULL_IN;

	rg.pc.leafs++;

	// Add all the surfaces
	for (i = 0, mark = leaf->firstMarkSurface; i < leaf->numMarkSurfaces; i++, mark++)
	{
		surface = *mark;

		if (surface->worldCount == rg.worldCount)
			continue;   // Already added this surface from another leaf
		surface->worldCount = rg.worldCount;

		if (!surface->texInfo->material->numStages)
			continue;   // Don't bother drawing

		// Add the surface
		R_AddSurface(surface, rg.worldEntity, rg.renderView.viewOrigin, clipFlags);
	}
}

/*
 ==================
 R_AddWorld
 ==================
*/
void R_AddWorld ()
{
	if (!rg.renderViewParms.primaryView)
		return;

	// Bump world count
	rg.worldCount++;

	// Auto cycle the world frame for texture animation
	rg.worldEntity->frame = (int)(rg.renderView.time * 2);

	// Clear visible mins/maxs
	ClearBounds(rg.renderViewParms.visMins, rg.renderViewParms.visMaxs);

	// Mark visible leaves
	R_MarkLeaves();

	// Recurse down the BSP tree
	R_RecursiveWorldNode(rg.worldModel->nodes, rg.renderViewParms.planeBits);
}